package org.fhnw.aigs.commons; import com.sun.org.apache.xml.internal.serializer.OutputPropertiesFactory; import java.io.File; import java.io.IOException; import java.io.StringReader; import java.io.StringWriter; import org.fhnw.aigs.commons.communication.Message; import java.util.logging.Level; import javax.xml.bind.*; import javax.xml.transform.*; import javax.xml.transform.stream.*; /** * This class provides XML-centred helper methods.<br> * v1.0 Initial release<br> * v1.1 Added some further error handling<br> * v1.2 Changing of logging<br> * v1.3 Adding discard option to avoid problems with malformed xml inputs * * @author Matthias Stöckli (v1.0) * @version v1.3 (Raphael Stoeckli, 21.04.2015) */ public class XMLHelper { /** * This method returns a {@link javax.xml.bind.Marshaller} . This object * belongs to {@link JAXBContext} which is responsible for unmarshalling XML * plain text into objects. <br> * This method modifies the mechanism of JAXB slightly so that it produces * unformatted XML output. In consequence, the XML will contain no line * breaks. This is useful when sending it to the server or the clients as * the client or the server can just read the first line from the buffer and * immediately interpret it as XML. * * @param message The message to be unformatted. * @return The resulting Marshaller */ public static Marshaller getUnformattedXMLMarshaller(Message message) { // HACK: The very first call of a running game can cause a crash in marshaller. All further attemps running without problems. Problem with ClassLoader??? JAXBContext context; Marshaller marshaller = null; String errorMessage = ""; Exception outputException = null; boolean processingError = false; for(int i = 0; i < 5; i++) // In case of an exception, let's try n times and then give up (and handle error message) { try { context = JAXBContext.newInstance(message.getClass()); marshaller = context.createMarshaller(); // Set the JAXB_FORMATTED_OUTPUT property. marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.FALSE); return marshaller; } catch (JAXBException ex) { //Logger.getLogger(XMLHelper.class.getName()).log(Level.SEVERE, null, ex); errorMessage = ""; outputException = ex; processingError = true; } catch (Exception ex) // All other exceptions { errorMessage = "An unknown error occured."; outputException = ex; processingError = true; // Logger.getLogger(XMLHelper.class.getName()).log(Level.SEVERE, "An unknown error occured.", ex); } } if (processingError == true) { // Logger.getLogger(XMLHelper.class.getName()).log(Level.SEVERE, errorMessage, outputException); LogRouter.log(XMLHelper.class.getName(), Level.SEVERE, errorMessage, outputException); } return marshaller; } /** * Pretty prints/formats XML. Inspired by "zerix". See * http://www.tutorials.de/java/276286-xmlstring-xml-format-ausgeben-lassen.html * @return returns formated XML string * @param inputString Unformatted XML input or plain text (must be discarded). * @param discardTransformationErrors If true, transformation errors are discarded and the input string is passed as output */ public static String prettyPrintXml(String inputString, boolean discardTransformationErrors) { try { StringWriter writer = new StringWriter(); // Use Transformer to format the string Transformer transformer = TransformerFactory.newInstance().newTransformer(); transformer.setOutputProperty(OutputKeys.INDENT, "yes"); transformer.setOutputProperty(OutputPropertiesFactory.S_KEY_INDENT_AMOUNT, "1"); transformer.transform(new StreamSource(new StringReader(inputString)), new StreamResult(writer)); StringBuffer buffer = writer.getBuffer(); return buffer.toString(); } catch (TransformerConfigurationException ex) { //Logger.getLogger(XMLHelper.class.getName()).log(Level.SEVERE, null, ex); LogRouter.log(XMLHelper.class.getName(), Level.SEVERE, null, ex); } catch (TransformerException ex) { //Logger.getLogger(XMLHelper.class.getName()).log(Level.SEVERE, null, ex); if (discardTransformationErrors == true) { return inputString; } else { LogRouter.log(XMLHelper.class.getName(), Level.SEVERE, null, ex); } } catch (Exception ex) // All other exceptions { //Logger.getLogger(XMLHelper.class.getName()).log(Level.SEVERE, "An unknown error occured.", ex); LogRouter.log(XMLHelper.class.getName(), Level.SEVERE, "An unknown error occured.", ex); } return ""; } /** * Pretty prints/formats XML. Inspired by "zerix". See * http://www.tutorials.de/java/276286-xmlstring-xml-format-ausgeben-lassen.html * @return returns formated XML string * @param inputString Unformatted XML input. Plain text will throw an exception. <br>Use {@link XMLHelper#prettyPrintXml(java.lang.String, boolean) } to handle plain text. * @since v1.3 */ public static String prettyPrintXml(String inputString) { return XMLHelper.prettyPrintXml(inputString, false); } /** * Use this main method if you want to generate XSDs out of the message * classes. * * @param args * @throws IOException * @throws ClassNotFoundException */ public static void main(String[] args) throws IOException, ClassNotFoundException { generateXSDs(); } /** * Creates XSDs using the generateSchema method of JAXBContext. Based on * http://stackoverflow.com/questions/7212064/is-it-possible-to-generate-a-xsd-from-a-jaxb-annotated-class */ private static void generateXSDs() throws IOException, ClassNotFoundException { // List of all message classes String[] messageClassNames = new String[]{"BadInputMessage", "ClientClosedMessage", "ExceptionMessage", "FieldChangedMessage", "FieldClickFeedbackMessage", "FieldClickMessage", "GameEndsMessage", "GameStartMessage", "IdentificationMessage", "IdentificationResponseMessage", "JoinMessage", "KeepAliveMessage", "PlayerChangedMessage", "ResultMessage", "Message"}; JAXBContext jaxbContext; try { Class messageClazz = null; // Load all the classes and create a JAXBContext out of it // then generate a schema using a SchemaOutputResolver. for (String messageClassName : messageClassNames) { messageClazz = Class.forName("org.fhnw.aigs.commons.communication." + messageClassName); jaxbContext = JAXBContext.newInstance(messageClazz); SchemaOutputResolver sor = new XMLHelper.XSDOutputResolver(messageClassName); jaxbContext.generateSchema(sor); } } catch (JAXBException ex) { //Logger.getLogger(XMLHelper.class.getName()).log(Level.SEVERE, null, ex); LogRouter.log(XMLHelper.class.getName(), Level.SEVERE, null, ex); } catch (IOException | ClassNotFoundException ex) // All other exceptions { // Logger.getLogger(XMLHelper.class.getName()).log(Level.SEVERE, "An unknown error occured.", ex); LogRouter.log(XMLHelper.class.getName(), Level.SEVERE, "An unknown error occured.", ex); } } /** * Generates XSDs, use it with JAXB. */ private static class XSDOutputResolver extends SchemaOutputResolver { // File name of XSD file String fileName; private XSDOutputResolver(String messageClassName) { this.fileName = messageClassName; } /** * Save the schema in the folder "Schemata". * * @param namespaceUri The namespace. * @param suggestedFileName Automatically generated name. * @return The result (file) * @throws IOException */ @Override public Result createOutput(String namespaceUri, String suggestedFileName) throws IOException { File file = new File("..\\Schemata\\" + fileName + ".xsd"); StreamResult result = new StreamResult(file); result.setSystemId(file.toURI().toURL().toString()); return result; } } }